using System;
using System.Collections;
using System.Collections.Generic;

namespace LightCAD.Three
{
    public class StereoCameraCache
    {
        public double focus = double.NaN;
        public double fov = double.NaN;
        public double aspect = double.NaN;
        public double near = double.NaN;
        public double far = double.NaN;
        public double zoom = double.NaN;
        public double eyeSep = double.NaN;
    }

    public class StereoCamera : Camera
    {
        #region scope properties or methods
        //private static Matrix4 _eyeRight = new Matrix4();
        //private static Matrix4 _eyeLeft = new Matrix4();
        //private static Matrix4 _projectionMatrix = new Matrix4();
        public StereoCameraContext getContext()
        {
            return ThreeThreadContext.GetCurrThreadContext().StereoCameraCtx;
        }
        #endregion

        #region Properties

        public double aspect;
        public double eyeSep;
        public PerspectiveCamera cameraL;
        public PerspectiveCamera cameraR;
        public StereoCameraCache _cache;

        #endregion

        #region constructor
        public StereoCamera()
        {
            this.type = "StereoCamera";
            this.aspect = 1;
            this.eyeSep = 0.064;
            this.cameraL = new PerspectiveCamera();
            this.cameraL.layers.enable(1);
            this.cameraL.matrixAutoUpdate = false;
            this.cameraR = new PerspectiveCamera();
            this.cameraR.layers.enable(2);
            this.cameraR.matrixAutoUpdate = false;
            this._cache = new StereoCameraCache();
        }
        #endregion

        #region methods
        public void update(PerspectiveCamera camera)
        {
            var cache = this._cache;
            var needsUpdate = cache.focus != camera.focus
                            || cache.fov != camera.fov
                            || cache.aspect != camera.aspect * this.aspect
                            || cache.near != camera.near
                            || cache.far != camera.far
                            || cache.zoom != camera.zoom
                            || cache.eyeSep != this.eyeSep;
            var ctx = getContext();
            var _projectionMatrix = ctx._projectionMatrix;
            var _eyeLeft = ctx._eyeLeft;
            var _eyeRight = ctx._eyeRight;
            if (needsUpdate)
            {


                cache.focus = camera.focus;
                cache.fov = camera.fov;
                cache.aspect = camera.aspect * this.aspect;
                cache.near = camera.near;
                cache.far = camera.far;
                cache.zoom = camera.zoom;
                cache.eyeSep = this.eyeSep;
                // Off-axis stereoscopic effect based on
                // http://paulbourke.net/stereographics/stereorender/
                _projectionMatrix.Copy(camera.projectionMatrix);
                var eyeSepHalf = cache.eyeSep / 2;
                var eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus;
                var ymax = (cache.near * Math.Tan(MathEx.DEG2RAD * cache.fov * 0.5)) / cache.zoom;
                double xmin, xmax;
                // translate xOffset
                _eyeLeft.Elements[12] = -eyeSepHalf;
                _eyeRight.Elements[12] = eyeSepHalf;
                // for left eye
                xmin = -ymax * cache.aspect + eyeSepOnProjection;
                xmax = ymax * cache.aspect + eyeSepOnProjection;
                _projectionMatrix.Elements[0] = 2 * cache.near / (xmax - xmin);
                _projectionMatrix.Elements[8] = (xmax + xmin) / (xmax - xmin);
                this.cameraL.projectionMatrix.Copy(_projectionMatrix);
                // for right eye
                xmin = -ymax * cache.aspect - eyeSepOnProjection;
                xmax = ymax * cache.aspect - eyeSepOnProjection;
                _projectionMatrix.Elements[0] = 2 * cache.near / (xmax - xmin);
                _projectionMatrix.Elements[8] = (xmax + xmin) / (xmax - xmin);
                this.cameraR.projectionMatrix.Copy(_projectionMatrix);
            }
            this.cameraL.matrixWorld.Copy(camera.matrixWorld).Multiply(_eyeLeft);
            this.cameraR.matrixWorld.Copy(camera.matrixWorld).Multiply(_eyeRight);
        }
        #endregion

    }
}
